/*
 * Copyright (c) 2018-2024, ARM Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

/*
 * If a platform wishes to use the functions in this file it has to be added to
 * the Makefile of the platform. It is not included in the common Makefile.
 */

#include <asm_macros.S>
#include <drivers/console.h>

	.globl	plat_crash_console_init
	.globl	plat_crash_console_putc
	.globl	plat_crash_console_flush

	/*
	 * Spinlock to syncronize access to crash_console_triggered. We cannot
	 * acquire spinlocks when the cache is disabled, so in some cases (like
	 * late during CPU suspend) some risk remains.
	 */
.section .data.crash_console_spinlock
	define_asm_spinlock crash_console_spinlock

	/*
	 * Flag to make sure that only one CPU can write a crash dump even if
	 * multiple crash at the same time. Interleaving crash dumps on the same
	 * console would just make the output unreadable, so it's better to only
	 * get a single but uncorrupted dump. This also means that we don't have
	 * to duplicate the reg_stash below for each CPU.
	 */
.section .data.crash_console_triggered
	crash_console_triggered: .byte 0

	/*
	 * Space to stash away some register values while we're calling into
	 * console drivers and don't have a real stack available. We need x14,
	 * x15 and x30 for bookkeeping within the plat_crash_console functions
	 * themselves, and some console drivers use x16 and x17 as additional
	 * scratch space that is not preserved by the main crash reporting
	 * framework. (Note that x16 and x17 should really never be expected to
	 * retain their values across any function call, even between carefully
	 * designed assembly functions, since the linker is always free to
	 * insert a function call veneer that uses these registers as scratch
	 * space at any time. The current crash reporting framework doesn't
	 * really respect that, but since TF is usually linked as a single
	 * contiguous binary of less than 128MB, it seems to work in practice.)
	 */
.section .data.crash_console_reg_stash
	.align 3
	crash_console_reg_stash: .quad 0, 0, 0, 0, 0

	/* --------------------------------------------------------------------
	 * int plat_crash_console_init(void)
	 * Takes the crash console spinlock (if possible) and checks the trigger
	 * flag to make sure we're the first CPU to dump. If not, return an
	 * error (so crash dumping will fail but the CPU will still call
	 * plat_panic_handler() which may do important platform-specific tasks
	 * that may be needed on all crashing CPUs). In either case, the lock
	 * will be released so other CPUs can make forward progress on this.
	 * Clobbers: x0 - x4, x30
	 * --------------------------------------------------------------------
	 */
func plat_crash_console_init
#if defined(IMAGE_BL31)
	mov	x4, x30		/* x3 and x4 are not clobbered by spin_lock() */
	mov	x3, #0		/* return value */

	adrp	x0, crash_console_spinlock
	add	x0, x0, :lo12:crash_console_spinlock

	mrs	x1, sctlr_el3
	tst	x1, #SCTLR_C_BIT
	beq	skip_spinlock	/* can't synchronize when cache disabled */
	bl	spin_lock

skip_spinlock:
	adrp	x1, crash_console_triggered
	add	x1, x1, :lo12:crash_console_triggered
	ldarb	w2, [x1]
	cmp	w2, #0
	bne	init_error

	mov	x3, #1		/* set return value to success */
	stlrb	w3, [x1]

init_error:
	bl	spin_unlock	/* harmless if we didn't acquire the lock */
	mov	x0, x3
	ret	x4
#else	/* Only one CPU in BL1/BL2, no need to synchronize anything */
	mov	x0, #1
	ret
#endif
endfunc plat_crash_console_init

	/* --------------------------------------------------------------------
	 * int plat_crash_console_putc(char c)
	 * Prints the character on all consoles registered with the console
	 * framework that have CONSOLE_FLAG_CRASH set. Note that this is only
	 * helpful for crashes that occur after the platform initialization code
	 * has registered a console. Platforms using this implementation need to
	 * ensure that all console drivers they use that have the CRASH flag set
	 * support this (i.e. are written in assembly and comply to the register
	 * clobber requirements of plat_crash_console_putc().
	 * --------------------------------------------------------------------
	 */
func plat_crash_console_putc
	adrp	x1, crash_console_reg_stash
	add	x1, x1, :lo12:crash_console_reg_stash
	stp	x14, x15, [x1]
	stp	x16, x17, [x1, #16]
	str	x30, [x1, #32]

	mov	w14, w0				/* W14 = character to print */
	adrp	x15, console_list
	ldr	x15, [x15, :lo12:console_list]	/* X15 = first console struct */

putc_loop:
	cbz	x15, putc_done
	ldr	w1, [x15, #CONSOLE_T_FLAGS]
	tst	w1, #CONSOLE_FLAG_CRASH
	b.eq	putc_continue
	ldr	x2, [x15, #CONSOLE_T_PUTC]
	cbz	x2, putc_continue
	cmp	w14, #'\n'
	b.ne	putc
	tst	w1, #CONSOLE_FLAG_TRANSLATE_CRLF
	b.eq	putc
	mov	x1, x15
	mov	w0, #'\r'
	blr	x2
	ldr	x2, [x15, #CONSOLE_T_PUTC]
putc:
	mov	x1, x15
	mov	w0, w14
	blr	x2
putc_continue:
	ldr	x15, [x15]			/* X15 = next struct */
	b	putc_loop

putc_done:
	adrp	x1, crash_console_reg_stash
	add	x1, x1, :lo12:crash_console_reg_stash
	ldp	x14, x15, [x1]
	ldp	x16, x17, [x1, #16]
	ldr	x30, [x1, #32]
	ret
endfunc plat_crash_console_putc

	/* --------------------------------------------------------------------
	 * int plat_crash_console_flush(char c)
	 * Flushes all consoles registered with the console framework that have
	 * CONSOLE_FLAG_CRASH set. Same requirements as putc().
	 * --------------------------------------------------------------------
	 */
func plat_crash_console_flush
	adrp	x1, crash_console_reg_stash
	add	x1, x1, :lo12:crash_console_reg_stash
	stp	x30, x15, [x1]
	stp	x16, x17, [x1, #16]

	adrp	x15, console_list
	ldr	x15, [x15, :lo12:console_list]	/* X15 = first console struct */

flush_loop:
	cbz	x15, flush_done
	ldr	w1, [x15, #CONSOLE_T_FLAGS]
	tst	w1, #CONSOLE_FLAG_CRASH
	b.eq	flush_continue
	ldr	x2, [x15, #CONSOLE_T_FLUSH]
	cbz	x2, flush_continue
	mov	x0, x15
	blr	x2
flush_continue:
	ldr	x15, [x15]			/* X15 = next struct */
	b	flush_loop

flush_done:
	adrp	x1, crash_console_reg_stash
	add	x1, x1, :lo12:crash_console_reg_stash
	ldp	x30, x15, [x1]
	ldp	x16, x17, [x1, #16]
	ret
endfunc plat_crash_console_flush
